package worker

import (
	"GoCrontab/crontab/common"
	"context"
	"github.com/coreos/etcd/clientv3"
)

type JobLock struct {
	kv      clientv3.KV
	lease   clientv3.Lease
	jobName string
	//用于终止自动续约
	cancelFunc context.CancelFunc
	//租约的id
	leaseId  clientv3.LeaseID
	isLocked bool
}

func InitJobLock(jobName string, kv clientv3.KV, lease clientv3.Lease) (jobLock *JobLock) {

	jobLock = &JobLock{
		kv:      kv,
		lease:   lease,
		jobName: jobName,
	}
	return
}

func (jobLock *JobLock) TryLock() (err error) {

	var (
		//租约返回结果
		leaseGrantResp     *clientv3.LeaseGrantResponse
		cancelFunc         context.CancelFunc
		cancelCtx          context.Context
		leaseId            clientv3.LeaseID
		leaseKeepAliveChan <-chan *clientv3.LeaseKeepAliveResponse
		txn                clientv3.Txn
		lockKey            string
		txnResponse        *clientv3.TxnResponse
	)

	//1. 创建租约
	if leaseGrantResp, err = jobLock.lease.Grant(context.TODO(), 5); err != nil {
		goto FAIL
	}
	// 用于设置取消自动续约
	cancelCtx, cancelFunc = context.WithCancel(context.TODO())

	leaseId = leaseGrantResp.ID

	if leaseKeepAliveChan, err = jobLock.lease.KeepAlive(cancelCtx, leaseId); err != nil {
		goto FAIL
	}
	// 3. 处理续租应答的协程
	go func() {
		var (
			keepResp *clientv3.LeaseKeepAliveResponse
		)

		for {
			select {
			//自动续租的应答
			case keepResp = <-leaseKeepAliveChan:
				if keepResp == nil {
					goto END
				}
			}
		}
	END:
	}()

	//4. 创建事务txn

	txn = jobLock.kv.Txn(context.TODO())

	lockKey = common.JOB_LOCK_DIR + jobLock.jobName

	//防止锁被长期占用，这里用lease keepAlive，
	txn.If(clientv3.Compare(clientv3.CreateRevision(lockKey), "=", 0)).
		Then(clientv3.OpPut(lockKey, "", clientv3.WithLease(leaseId))).
		Else(clientv3.OpGet(lockKey))

	//提交事务
	if txnResponse, err = txn.Commit(); err != nil {
		goto FAIL
	}

	//6. 失效释放租约
	if !txnResponse.Succeeded {
		err = common.ERR_LOCK_ALREADY_REQUIRED
		goto FAIL
	}
	// 抢锁成功

	jobLock.leaseId = leaseId
	jobLock.cancelFunc = cancelFunc
	jobLock.isLocked = true
	return
FAIL:
	//调用取消租约回调
	cancelFunc()
	//马上释放租约
	jobLock.lease.Revoke(context.TODO(), leaseId)
	return
}

//锁释放
func (jobLock *JobLock) Unlock() {
	if jobLock.isLocked {
		//取消自动续约的协程
		jobLock.cancelFunc()
		jobLock.lease.Revoke(context.TODO(), jobLock.leaseId)
	}
}
